﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.IO;
using System.Linq;
using EnvDTE;
using VsxFactory.Modeling.Repository;
using VsxFactory.Modeling.Strategies;
using VsxFactory.Modeling.Strategies.VisualStudio;
using VsxFactory.Modeling.StrategyManager.Strategies;
using VsxFactory.Modeling.VisualStudio.Synchronization;
using VsxFactory.Modeling.VisualStudio;
using Microsoft.VisualStudio.Modeling.Shell;
using Microsoft.VisualStudio.Modeling;
using VsxFactory.Modeling.StrategyManager.Dialog;
using Microsoft.VisualStudio.Shell.Interop;

namespace VsxFactory.Modeling.StrategyManager
{
    class StrategyManager : IDisposable, VsxFactory.Modeling.Strategies.IStrategyManager, IStrategiesHost
    {
        private VisualStudioStrategyContext context;
        private Microsoft.VisualStudio.Shell.Package package;
        private SolutionEvents solutionEvents;
        private DocumentEvents documentsEvents;
        private SolutionBuildListener buildListener;
        private bool disposed;
        [ImportMany("Provider", typeof(IStrategyProvider), AllowRecomposition=true)]
        private IEnumerable<Lazy<IStrategyProvider, IStrategyProviderMetadata>> strategyProviders=null;
        private List<IStrategyProvider> activeStrategyProviders = new List<IStrategyProvider>();
        private Dictionary<string, List<IStrategy>> activeStrategies = new Dictionary<string, List<IStrategy>>();
        private List<IPostBuildStrategy> buildStrategies = new List<IPostBuildStrategy>();
        private ProjectItemsEvents projectItemsEvents;
        private CodeTypesObserver codeTypesObserver;
        private string shadowCopyFolder;

        public StrategyManager(Microsoft.VisualStudio.Shell.Package package)
        {
            System.Diagnostics.Contracts.Contract.Requires(package != null);

            this.package = package;
            SetSite(package);
            var shadowCopyBaseFolder = Path.Combine( Utils.GetTempFolderBase(), "ShadowCopies" );
            shadowCopyFolder = Path.Combine(shadowCopyBaseFolder, Guid.NewGuid().ToString());

            // On va supprimer les anciens répertoires qui trainent
            if (Directory.Exists(shadowCopyBaseFolder))
            {
                foreach (var folder in Directory.GetDirectories(shadowCopyBaseFolder))
                {
                    Utils.RemoveDirectory(folder); // safe remove
                }
            }

            Directory.CreateDirectory(shadowCopyFolder);
        }

        public void SetSite(IServiceProvider serviceProvider)
        {
            var dte = (DTE)ServiceProvider.GetService(typeof(DTE));
            documentsEvents = dte.Events.DocumentEvents;
            
            projectItemsEvents = dte.Events.SolutionItemsEvents;
            documentsEvents.DocumentOpened += new _dispDocumentEvents_DocumentOpenedEventHandler(OnDocumentOpened);
            documentsEvents.DocumentSaved += new _dispDocumentEvents_DocumentSavedEventHandler(OnDocumentSaved);
            documentsEvents.DocumentClosing += new _dispDocumentEvents_DocumentClosingEventHandler(OnDocumentClosing);
            
            solutionEvents = dte.Events.SolutionEvents;
            solutionEvents.Opened += new _dispSolutionEvents_OpenedEventHandler(OnSolutionOpened);
            solutionEvents.BeforeClosing += new _dispSolutionEvents_BeforeClosingEventHandler(OnSolutionBeforeClosing);
            
            buildListener = new SolutionBuildListener(ServiceProvider);
            buildListener.BuildCompleted += new EventHandler<BuildCompleteEventArgs>(buildListener_BuildCompleted);
            //codeTypesObserver = new CodeTypesObserver(ServiceProvider);
            //codeTypesObserver.OnParameterAdded += new Strategies.ParameterAdded(codeTypesObserver_OnParameterAdded);
            //codeTypesObserver.OnParameterRemoved += new Strategies.ParameterRemoved(codeTypesObserver_OnParameterRemoved);
            //codeTypesObserver.OnParameterRenamed += new Strategies.ParameterRenamed(codeTypesObserver_OnParameterRenamed);
            //codeTypesObserver.OnAttributeAdded += new Strategies.AttributeAdded(codeTypesObserver_OnAttributeAdded);
            //codeTypesObserver.OnAttributeChanged += new Strategies.AttributeChanged(codeTypesObserver_OnAttributeChanged);
            //codeTypesObserver.OnAttributeRemoved += new Strategies.AttributeRemoved(codeTypesObserver_OnAttributeRemoved);
            //codeTypesObserver.OnBaseTypeChanged += new Strategies.BaseTypeChanged(codeTypesObserver_OnBaseTypeChanged);
            //codeTypesObserver.OnElementPropertiesChanged += new Strategies.ElementPropertiesChanged(codeTypesObserver_OnElementPropertiesChanged);
            //codeTypesObserver.OnFieldAdded += new Strategies.FieldAdded(codeTypesObserver_OnFieldAdded);
            //codeTypesObserver.OnFieldRemoved += new Strategies.MemberRemoved(codeTypesObserver_OnFieldRemoved);
            //codeTypesObserver.OnFieldRenamed += new Strategies.FieldRenamed(codeTypesObserver_OnFieldRenamed);
            //codeTypesObserver.OnMethodAdded += new Strategies.MethodAdded(codeTypesObserver_OnMethodAdded);
            //codeTypesObserver.OnMethodRemoved += new Strategies.MemberRemoved(codeTypesObserver_OnMethodRemoved);
            //codeTypesObserver.OnMethodRenamed += new Strategies.MethodRenamed(codeTypesObserver_OnMethodRenamed);
            //codeTypesObserver.OnNestedTypeAdded += new Strategies.NestedTypeAdded(codeTypesObserver_OnNestedTypeAdded);
            //codeTypesObserver.OnPropertyAdded += new Strategies.PropertyAdded(codeTypesObserver_OnPropertyAdded);
            //codeTypesObserver.OnPropertyRemoved += new Strategies.MemberRemoved(codeTypesObserver_OnPropertyRemoved);
            //codeTypesObserver.OnTypeAdded += new Strategies.TypeAdded(codeTypesObserver_OnTypeAdded);
            //codeTypesObserver.OnTypeRemoved += new Strategies.TypeRemoved(codeTypesObserver_OnTypeRemoved);
            //codeTypesObserver.OnTypeRenamed += new Strategies.TypeRenamed(codeTypesObserver_OnTypeRenamed);
        }

        #region Code type events
        void codeTypesObserver_OnTypeRenamed(string oldFullName, CodeType type)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnTypeRenamed(oldFullName, node);
            string actionName = "On type renamed";
            var documentFileName = type.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnTypeRemoved(CodeType type, string fullName)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnTypeRenamed(fullName, node);
            string actionName = "On type removed";
            var documentFileName = type.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnTypeAdded(CodeType type)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnTypeAdded(node, type);
            string actionName = "On type added";
            var documentFileName = type.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnPropertyRemoved(CodeType type, string memberNameOrSignature)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnPropertyRemoved(node, type, memberNameOrSignature);
            string actionName = "On property removed";
            var documentFileName = type.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnPropertyAdded(CodeType parent, CodeProperty newProperty)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnPropertyAdded(node, parent, newProperty);
            string actionName = "On property added";
            var documentFileName = newProperty.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnNestedTypeAdded(CodeClass parent, CodeType newNestedType)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnNestedTypeAdded(node, parent, newNestedType);
            string actionName = "On nested type added";
            var documentFileName = newNestedType.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnMethodRenamed(CodeType type, EnvDTE80.CodeFunction2 method)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnMethodRenamed(node, type, method);
            string actionName = "On method renamed";
            var documentFileName = type.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnMethodRemoved(CodeType type, string memberNameOrSignature)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnMethodRemoved(node, type, memberNameOrSignature);
            string actionName = "On method removed";
            var documentFileName = type.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnMethodAdded(CodeType parent, EnvDTE80.CodeFunction2 newMethod)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnMethodAdded(node, parent, newMethod);
            string actionName = "On method added";
            var documentFileName = newMethod.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnFieldRenamed(CodeType type, CodeVariable field)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnFieldRenamed(node, type, field);
            string actionName = "On field renamed";
            var documentFileName = field.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnFieldRemoved(CodeType type, string memberNameOrSignature)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnFieldRemoved(node, type, memberNameOrSignature);
            string actionName = "On field removed";
            var documentFileName = type.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnFieldAdded(CodeType parent, CodeVariable newField)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnFieldAdded(node, parent, newField);
            string actionName = "On field added";
            var documentFileName = newField.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnElementPropertiesChanged(CodeElement element)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnElementPropertiesChanged(node, element);
            string actionName = "On element properties changed";
            var documentFileName = element.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnBaseTypeChanged(CodeType type)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnBaseTypeChanged(node, type);
            string actionName = "On base type changed";
            var documentFileName = type.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnAttributeRemoved(CodeElement parent, string attributeName)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnAttributeRemoved(node, parent, attributeName);
            string actionName = "On attribute removed";
            var documentFileName = parent.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnAttributeChanged(EnvDTE80.CodeAttribute2 attribute)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnAttributeChanged(node, attribute);
            string actionName = "On attribute changed";
            var documentFileName = attribute.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnAttributeAdded(EnvDTE80.CodeAttribute2 attribute)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnAttributeAdded(node, attribute);
            string actionName = "On attribute added";
            var documentFileName = attribute.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnParameterRenamed(EnvDTE80.CodeFunction2 method, int parameterIndex, CodeParameter parameter)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnParameterRenamed(node, method, parameterIndex, parameter);
            string actionName = "On parameter renamed";
            var documentFileName = method.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnParameterRemoved(EnvDTE80.CodeFunction2 method, string parameterName)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnParameterRemoved(node, method, parameterName);
            string actionName = "On parameter removed";
            var documentFileName = method.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        void codeTypesObserver_OnParameterAdded(EnvDTE80.CodeFunction2 method, CodeParameter parameter)
        {
            Action<ICodeEditorStrategy, IVsProjectFileItem> action = (strategy, node) => strategy.OnParameterAdded(node, method, parameter);
            string actionName = "On parameter added";
            var documentFileName = method.ProjectItem.FileNames[1];
            ExecuteCodeEditorStrategy(documentFileName, action, actionName);
        }

        private void ExecuteCodeEditorStrategy(string documentFileName, Action<ICodeEditorStrategy, IVsProjectFileItem> action, string actionName)
        {
            if (strategyProviders == null)
                return;
            if (SolutionExplorer.CurrentSolution == null)
                return;

            var node = SolutionExplorer.CurrentSolution.FindByPath<IVsProjectFileItem>(documentFileName);
            if (node == null)
                return;

            try
            {
                List<IStrategy> strategies;
                if (activeStrategies.TryGetValue(documentFileName, out strategies))
                {
                    foreach (var strategy in strategies.OfType<ICodeEditorStrategy>())
                    {
                        try
                        {
                            context.WriteLogMessage("-- Execute code editor strategy {0} on {1} event.", strategy.GetType(), actionName);
                            action(strategy, node);
                        }
                        catch (Exception ex)
                        {
                            context.WriteLogMessage("-- Error {0} when executing code editor strategy {1} on document {2}", ex.Message, strategy.GetType().Name, documentFileName);
                        }
                    }
                }
            }
            catch
            {
            }
        }
        #endregion

        public IServiceProvider ServiceProvider
        {
            get {return (IServiceProvider)package;}
        }

        public void InitializeServices(IServiceProvider serviceProvider, IServiceContainer serviceContainer)
        {
            System.Diagnostics.Contracts.Contract.Requires(serviceProvider != null);
            System.Diagnostics.Contracts.Contract.Requires(serviceContainer != null);

            //explorer = new VSSolutionExplorer(serviceProvider);
            //settings = new VisualStudioStrategySettings(serviceProvider);
            //serviceContainer.AddService(typeof(IVsSolutionExplorer), explorer);
            //serviceContainer.AddService(typeof(IStrategySettings), settings, true);
        }

        #region Show UI command
        public void CanExecuteShowUI(object sender, EventArgs e)
        {
            var command = sender as MenuCommand;
            var sln = context.SolutionExplorer.CurrentSolution;
            command.Visible = sln != null && !String.IsNullOrEmpty( sln.Path );
        }

        public void ExecuteShowUI(object sender, EventArgs e)
        {
            try
            {
                var wnd = new PackageManagerWindow(package, context, new VsPackageSourceProvider((VisualStudioStrategySettings)context.Settings), ()=>InitializeStrategies());
                wnd.Show();
            }
            catch
            {
            }
        }
        #endregion

        #region Generate command
        internal void ExecuteGenerate(object sender, EventArgs e)
        {
            try
            {
                if(CanExecuteGenerateOnSelectedItem())
                {
                    var command = sender as MenuCommand;
                    var item = context.SolutionExplorer.SelectedItem as IVsProjectFileItem;
                    if(item != null)
                        (( IStrategyManager )this).GenerateArtifacts(item);
                }
            }
            catch
            {
            }
        }

        internal void CanExecuteGenerate(object sender, EventArgs e)
        {
            var command = sender as MenuCommand;
            bool visible = CanExecuteGenerateOnSelectedItem();
            command.Visible = visible;
        }

        private bool CanExecuteGenerateOnSelectedItem()
        {
            var item = context.SolutionExplorer.SelectedItem;
            return CanExecuteGenerate(item as IVsProjectFileItem);
        }

        public bool CanExecuteGenerate(IVsProjectFileItem item)
        {
            bool visible = false;
            if (item != null)
            {
                List<IStrategy> strategies;
                if (activeStrategies.TryGetValue(item.Path, out strategies))
                {
                    foreach (var strategy in strategies.OfType<ISupportsGenerateCommand>())
                    {
                        visible |= strategy.CanExecuteGenerateCommand(item);
                    }
                }
            }
            return visible;
        }

        internal void ChangeGenerate(object sender, EventArgs e)
        {
            // Nothing
        }
        #endregion

        void buildListener_BuildCompleted(object sender, BuildCompleteEventArgs e)
        {
            if (buildStrategies.Count == 0)
                return;

            foreach (var strategy in buildStrategies)
            {
                try
                {
                    strategy.Execute(ServiceProvider, e);
                }
                catch (Exception ex2)
                {
                    Trace.WriteLine(String.Format("Erreur {0} when executing postbuild strategy {1}", ex2.Message, strategy.GetType().FullName));
                }
            }
        }

        /// <summary>
        /// MEF Composition
        /// </summary>
        private void SafeCompose(IVsActivityLog log) {
            try {
                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "MEF composition"));

                // Initialisation des catalogues avec les répertoires des stratégies activées dans la solution
                List<ComposablePartCatalog> catalogs = GetStrategiesCatalog();

                catalogs.Add(new AssemblyCatalog(this.GetType().Assembly));
                catalogs.Add(new AssemblyCatalog(typeof(IGenerationStrategy).Assembly));

                CompositionContainer container = new SafeCompositionContainer(catalogs, context, log);
                container.ComposeParts(this);
            }
            catch (Exception ex) {
                context.WriteError("Error when loading strategies ({0})", ex.Message);
                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "MEF composition error {0}", ex));
            }
        }

        /// <summary>
        /// Liste des catalogues pointant sur les répertoires contenant les stratégies
        /// </summary>
        /// <returns></returns>
        private List<ComposablePartCatalog> GetStrategiesCatalog()
        {
            // Pour permettre de pouvoir mettre à jour les stratégies si plusieurs instances de Visual Studio
            // sont actives, on va copier les stratégies dans un répertoire temporaire sur lesquels on va pointer.
            // On simule un shadow copy.
            List<ComposablePartCatalog> catalogs = new List<ComposablePartCatalog>();
            foreach (var package in context.SolutionRepository.GetPackages())
            {
                if (package.Kind == PackageKind.System)
                    continue;

                // On ne prend que les répertoires qui pointe sur la bonne version de la stratégie
                var folder = Settings.ResolvePackageFolder(package);
                if (Directory.Exists(folder))
                {
                    // Copie ds un répertoire temporaire
                    var targetFolder = Path.Combine(shadowCopyFolder, package.Name);
                    Utils.CopyDirectory(folder, targetFolder);
                    catalogs.Add(new DirectoryCatalog(targetFolder));
                    context.WriteLogMessage("Setting MEF catalog to temporary folder : {0} (shadow copy of {1})", targetFolder, folder);
                }
            }
            return catalogs;
        }

        /// <summary>
        /// 
        /// </summary>
        void OnSolutionBeforeClosing()
        {
            ClearStrategies();
        }

        private void ClearStrategies()
        {
            foreach(var p in activeStrategyProviders)
                p.Desactivate(ServiceProvider);

            activeStrategyProviders.Clear();
            activeStrategies.Clear();
            buildStrategies.Clear();
        }

        void OnSolutionOpened()
        {
            InitializeStrategies();
        }

        internal void InitializeStrategies()
        {
            // Activity log
            IVsActivityLog log = ServiceProvider.GetService(typeof(SVsActivityLog)) as IVsActivityLog;
            if (log != null)
                log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Entering initializeStrategies"));

            try
            {
                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "ClearStrategies"));

                ClearStrategies();

                var vsctx = new VsxFactory.Modeling.StrategyManager.Strategies.VisualStudioStrategyContext(ServiceProvider);
                context = vsctx;
                context.ClearErrors();

                if (SolutionExplorer.CurrentSolution == null || SolutionExplorer.CurrentSolution.Path == null)
                {
                    System.Threading.Thread.Sleep(2000); // Workaround : waiting solution is initialized
                    if (SolutionExplorer.CurrentSolution == null || SolutionExplorer.CurrentSolution.Path == null)
                        return;
                }

                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Instanciate context"));

                // Synchronization des strategies.
                SynchronizeStrategies(log, vsctx);

                SafeCompose(log);

                if (strategyProviders != null)
                {
                    ActivateStrategies(log);
                }
            }
            catch (Exception ex)
            {
                context.WriteError("Error during strategies initialization : You must restart Visual Studio. ({0})", ex);
                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Error during strategies initialization {0}", ex));
            }
        }

        private void ActivateStrategies(IVsActivityLog log)
        {
            foreach (var s in strategyProviders.Where(p => p.Metadata.IsInternal))
            {
                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Register standard strategy {0}", s.Metadata.PackageName));
                context.LocalRepository.RegisterStandardProvider(s);
            }


            if (log != null)
                log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Initialize strategies"));

            context.WriteLogMessage("Intialize strategies...");
            var localPackages = context.SolutionRepository.GetPackages();
            foreach (var provider in strategyProviders)
            {

                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Check strategy {0}", provider.Metadata.PackageName));

                if (!activeStrategyProviders.Contains(provider.Value) &&
                    localPackages.Any(p => p.Name == provider.Metadata.PackageName) &&
                    provider.Value.Activate(this, ServiceProvider))
                {
                    if (log != null)
                        log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Activate strategy {0}", provider.Metadata.PackageName));

                    context.WriteLogMessage("Activate strategy {0}", provider.Metadata.PackageName);
                    activeStrategyProviders.Add(provider.Value);
                }
            }

            var dte = ServiceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
            foreach (Document doc in dte.Documents)
            {
                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Call open document {0}", doc.Path));

                OnDocumentOpened(doc);
            }
        }

        private void SynchronizeStrategies(IVsActivityLog log, VisualStudioStrategyContext vsctx)
        {
            try
            {
                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Synchronize strategies"));

                vsctx.SolutionRepository.SynchronizeStrategies();
            }
            catch (Exception ex)
            {
                context.WriteLogMessage("Synchronization error : {0}", ex);
                if (log != null)
                    log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), string.Format(System.Globalization.CultureInfo.CurrentCulture, "Synchronization errors {0}", ex));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="document"></param>
        void OnDocumentClosing(Document document)
        {
            Trace.WriteLine(String.Format("Document {0} closed", document.Name));
            if (strategyProviders == null)
                return;

            if (SolutionExplorer.CurrentSolution == null)
                return;

            List<IStrategy> strategies;
            if (activeStrategies.TryGetValue(document.FullName, out strategies))
            {
                var node = SolutionExplorer.CurrentSolution.FindByPath<IVsProjectFileItem>(document.FullName);
                var newStrategiesList = new List<IStrategy>();
                foreach (var strategy in strategies)
                {
                    try
                    {
                        if (strategy is IGenerationStrategy)
                        {
                            if (node != null && !((IGenerationStrategy)strategy).OnDocumentClosed(node))
                                newStrategiesList.Add(strategy);
                        }
                        else if (strategy is ICodeEditorStrategy)
                        {
                            if (node != null && !((ICodeEditorStrategy)strategy).OnDocumentClosed(node))
                                newStrategiesList.Add(strategy);
                        }
                    }
                    catch (Exception ex)
                    {
                        context.WriteLogMessage("-- Error {0} when removing strategy {1} (document {2} closed)", ex.Message, strategy.GetType().Name, document.Name);
                    }
                }

                if (newStrategiesList.Count == 0)
                    activeStrategies.Remove(document.FullName);
                else
                    activeStrategies[document.FullName] = newStrategiesList;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="document"></param>
        void IStrategyManager.GenerateArtifacts(IVsProjectFileItem model)
        {
            System.Diagnostics.Contracts.Contract.Requires(model != null);
            if (strategyProviders == null)
                return;

            context.WriteLogMessage("Generate artifacts...");

            WaitDialog w = null;
            try
            {
                List<IStrategy> strategies;
                if (activeStrategies.TryGetValue(model.Path, out strategies))
                {
                    if (strategies.Count > 0 && w == null)
                        w = new WaitDialog(ServiceProvider, "Generation", "Generate artifacts...");
                    
                    foreach (var strategy in strategies.OfType<IGenerationStrategy>())
                    {
                        try
                        {
                            context.WriteLogMessage("-- Generate artifacts for strategy {0}", strategy.GetType());
                            strategy.GenerateArtifacts(model);
                        }
                        catch (Exception ex)
                        {
                            context.WriteLogMessage("-- Error {0} when generating artifacts with strategy {1}", ex.Message, strategy.GetType().Name);
                        }
                    }
                }
            }
            finally
            {
                if (w != null)
                    w.Dispose();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="document"></param>
        void OnDocumentSaved(Document document)
        {
            if (strategyProviders == null)
                return;

            if (SolutionExplorer.CurrentSolution == null)
                return;

            context.WriteLogMessage("Document {0} saved", document.Name);
            var node = SolutionExplorer.CurrentSolution.FindByPath<IVsProjectFileItem>(document.FullName);
            if (node == null)
                return;

            WaitDialog w = null;
            try
            {
                List<IStrategy> strategies;
                if (activeStrategies.TryGetValue(document.FullName, out strategies))
                {
                    if (strategies.Count > 0 && w != null)
                        w = new WaitDialog(ServiceProvider, "Generation", "Generate artifacts...");

                    foreach (var strategy in strategies.OfType<IGenerationStrategy>())
                    {
                        try
                        {
                            context.WriteLogMessage("-- Execute strategy {0}", strategy.GetType());
                            strategy.OnDocumentSaved(node);
                        }
                        catch (Exception ex)
                        {
                            context.WriteLogMessage("-- Error {0} when executing strategy {1} on document {2}", ex.Message, strategy.GetType().Name, document.Name);
                        }
                    }
                }
            }
            finally
            {
                if (w != null)
                    w.Dispose();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="document"></param>
        void OnDocumentOpened(Document document)
        {
            if (strategyProviders == null)
                return;

            if (SolutionExplorer.CurrentSolution == null)
                return;

            var node = SolutionExplorer.CurrentSolution.FindByPath<IVsProjectFileItem>(document.FullName);
            PerformDocumentOpened(node);
        }

        private void PerformDocumentOpened(IVsProjectFileItem node)
        {
            if (node == null)
                return;

            // Recherche des stratégies qui peuvent s'executer sur ce document
            foreach (var provider in activeStrategyProviders)
            {
                try
                {
                    var pi = node.ExternalObject as HierarchyNode;
                    var docdata = pi.GetDocData(Guid.Empty) as ModelingDocData;
                    Store store = null;
                    if (docdata != null)
                        store = docdata.Store;
                    var strategy = provider.CreateStrategy(node, store);
                    if (strategy != null)
                    {
                        if (strategy is IPostBuildStrategy)
                        {
                            if (!buildStrategies.Contains(strategy))
                            {
                                context.WriteLogMessage("Activate post build strategy {0} ({1})", strategy.GetType(), node.Name);
                                buildStrategies.Add(strategy as IPostBuildStrategy);
                            }
                        }
                        else
                        {
                            context.WriteLogMessage("Activate generation strategy {0} ({1})", strategy.GetType(), node.Name);

                            List<IStrategy> strategies;
                            if (activeStrategies.TryGetValue(node.Path, out strategies))
                            {
                                if (!strategies.Contains(strategy))                                
                                    strategies.Add(strategy);
                            }
                            else
                            {
                                strategies = new List<IStrategy>();
                                activeStrategies.Add(node.Path, strategies);
                                strategies.Add(strategy);
                            }
                        }
                    }
                }
                catch (Exception ex2)
                {
                    context.WriteLogMessage("Error {0} when activating strategy {1} on document {2}", ex2.Message, provider.GetType().Name, node.Name);
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~StrategyManager()
        {
            Dispose(false);
        }

        protected void Dispose(bool disposing)
        {
            if (!disposed)
            {
                try
                {
                    disposed = true;
                    if (disposing)
                    {
                        // Force le Dispose sur les ressources managés 
                    }

                    solutionEvents.Opened -= new _dispSolutionEvents_OpenedEventHandler(OnSolutionOpened);
                    solutionEvents.BeforeClosing -= new _dispSolutionEvents_BeforeClosingEventHandler(OnSolutionBeforeClosing);

                    documentsEvents.DocumentOpened -= new _dispDocumentEvents_DocumentOpenedEventHandler(OnDocumentOpened);
                    documentsEvents.DocumentSaved -= new _dispDocumentEvents_DocumentSavedEventHandler(OnDocumentSaved);
                    documentsEvents.DocumentClosing -= new _dispDocumentEvents_DocumentClosingEventHandler(OnDocumentClosing);
                    buildListener.BuildCompleted -= new EventHandler<BuildCompleteEventArgs>(buildListener_BuildCompleted);

                    //codeTypesObserver.OnParameterAdded -= new Strategies.ParameterAdded(codeTypesObserver_OnParameterAdded);
                    //codeTypesObserver.OnParameterRemoved -= new Strategies.ParameterRemoved(codeTypesObserver_OnParameterRemoved);
                    //codeTypesObserver.OnParameterRenamed -= new Strategies.ParameterRenamed(codeTypesObserver_OnParameterRenamed);
                    //codeTypesObserver.OnAttributeAdded -= new Strategies.AttributeAdded(codeTypesObserver_OnAttributeAdded);
                    //codeTypesObserver.OnAttributeChanged -= new Strategies.AttributeChanged(codeTypesObserver_OnAttributeChanged);
                    //codeTypesObserver.OnAttributeRemoved -= new Strategies.AttributeRemoved(codeTypesObserver_OnAttributeRemoved);
                    //codeTypesObserver.OnBaseTypeChanged -= new Strategies.BaseTypeChanged(codeTypesObserver_OnBaseTypeChanged);
                    //codeTypesObserver.OnElementPropertiesChanged -= new Strategies.ElementPropertiesChanged(codeTypesObserver_OnElementPropertiesChanged);
                    //codeTypesObserver.OnFieldAdded -= new Strategies.FieldAdded(codeTypesObserver_OnFieldAdded);
                    //codeTypesObserver.OnFieldRemoved -= new Strategies.MemberRemoved(codeTypesObserver_OnFieldRemoved);
                    //codeTypesObserver.OnFieldRenamed -= new Strategies.FieldRenamed(codeTypesObserver_OnFieldRenamed);
                    //codeTypesObserver.OnMethodAdded -= new Strategies.MethodAdded(codeTypesObserver_OnMethodAdded);
                    //codeTypesObserver.OnMethodRemoved -= new Strategies.MemberRemoved(codeTypesObserver_OnMethodRemoved);
                    //codeTypesObserver.OnMethodRenamed -= new Strategies.MethodRenamed(codeTypesObserver_OnMethodRenamed);
                    //codeTypesObserver.OnNestedTypeAdded -= new Strategies.NestedTypeAdded(codeTypesObserver_OnNestedTypeAdded);
                    //codeTypesObserver.OnPropertyAdded -= new Strategies.PropertyAdded(codeTypesObserver_OnPropertyAdded);
                    //codeTypesObserver.OnPropertyRemoved -= new Strategies.MemberRemoved(codeTypesObserver_OnPropertyRemoved);
                    //codeTypesObserver.OnTypeAdded -= new Strategies.TypeAdded(codeTypesObserver_OnTypeAdded);
                    //codeTypesObserver.OnTypeRemoved -= new Strategies.TypeRemoved(codeTypesObserver_OnTypeRemoved);
                    //codeTypesObserver.OnTypeRenamed -= new Strategies.TypeRenamed(codeTypesObserver_OnTypeRenamed);                
                }
                catch
                {

                }
            }
        }

        public IStrategyContext GetStrategyContext(IGenerationStrategy strategy, IVsProjectFileItem model, CodeGenerationEvent generationEvent)
        {
            System.Diagnostics.Contracts.Contract.Requires(strategy != null);
            System.Diagnostics.Contracts.Contract.Requires(model != null);
            context.SetContextForStrategy(strategy, model, generationEvent);
            return context;
        }

        public IStrategyContext Context
        {
            get { return context; }
        }

        public IStrategySettings Settings
        {
            get { return context.Settings; }
        }

        public IVsSolutionExplorer SolutionExplorer
        {
            get { return context.SolutionExplorer; }
        }

        public void Run(StrategyContext context, System.Threading.CancellationToken cts)
        {
            throw new NotImplementedException();
        }
    }
}
